{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": false
   },
   "source": [
    "# 6.1. 从全连接层到卷积\n",
    ":label:`sec_why-conv`\n",
    "\n",
    "我们之前讨论的多层感知机十分适合处理表格数据。表格数据中的每行对应每个样本，每列分别对应每个特征。这些特征之间的交互可能产生影响，但我们没有考虑特征交互结构上的先验假设。\n",
    "\n",
    "有时我们缺乏足够的知识来指导更巧妙的模型结构设计，此时多层感知机可能是最好的选择。然而，对于高维感知数据，这种无结构网络可能会变得笨拙。\n",
    "\n",
    "例如，在之前区分猫和狗的例子中。假设我们收集了一个照片数据集，每张照片具有百万级像素，这意味着多层感知机的每次输入都有一百万个维度。\n",
    "根据我们在 :numref:`subsec_parameterization-cost-fc-layers` 中对全连接层参数开销的讨论。即使将隐藏层维度降低到 $1000$ ，这个神经网络也将有 $10^6 \\times 10^3 = 10^9$ 个参数。想要训练这个模型很难，需要有大量的GPU、分布式优化训练的经验和超乎常人的耐心。\n",
    "\n",
    "\n",
    "细心的读者可能会反对这一论点，认为要求百万像素的分辨率可能不是必要的。\n",
    "然而，即使减小为十万像素，$1000$ 个隐藏单元的隐藏层也可能不足以学习到良好的图像特征，所以我们仍然需要数十亿个参数。\n",
    "此外，拟合如此多的参数还需要收集大量的数据。\n",
    "然而，如今人类视觉和传统机器学习模型都能很好地区分猫和狗。这是因为图像中有丰富的结构，人类和机器学习模型都可以利用这些结构。\n",
    "*卷积神经网络*（convolutional neural networks，CNN）是机器学习利用自然图像中一些已知结构的创造性方法。\n",
    "\n",
    "\n",
    "## 6.1.1. 不变性\n",
    "\n",
    "想象一下，你想对一张图片中的物体进行检测。合理的做法是，我们使用的无论哪种方法都应该和物体的位置无关。\n",
    "虽然猪通常不在天上飞，飞机通常不在水里游泳，我们仍然应该意识到猪有可能出现在图片的上方。\n",
    "我们可以从儿童游戏 ”沃尔多在哪里”（ :numref:`img_waldo` ）中汲取一些灵感。\n",
    "这个游戏包含许多混乱的场景，游戏玩家的目标是找到沃尔多，而沃尔多通常潜伏在一些不太可能的位置。\n",
    "所以尽管沃尔多的样子很有特点，在眼花缭乱的场景中找到他也如大海捞针。\n",
    "\n",
    "然而沃尔多的样子并不取决于他潜藏的地方。我们可以使用一个“沃尔多检测器”扫描图像，该检测器将图像分成数个区域，并为每个区域包含沃尔多的可能性打分。卷积神经网络正是将“空间不变性”的这一概念系统化，用较少的参数来学习有用的特征。\n",
    "\n",
    "\n",
    "![沃尔多游戏示例图。](../img/where-wally-walker-books.jpg)\n",
    ":width:`400px`\n",
    ":label:`img_waldo`\n",
    "\n",
    "\n",
    "现在，我们将上面想法总结一下，从而帮助我们设计适合于计算机视觉的神经网络结构：\n",
    "\n",
    "1. 平移不变性：不管出现在图像中的哪个位置，神经网络的底层应该对相同的图像区域做出类似的响应。这个原理即为“平移不变性”。\n",
    "1. 局部性：神经网络的底层应该只探索输入图像中的局部区域，而不考虑图像远处区域的内容，这就是“局部性”原则。最终，这些局部特征可以融会贯通，在整个图像级别上做出预测。\n",
    "\n",
    "让我们看看这是如何转化为数学表示的。\n",
    "\n",
    "\n",
    "\n",
    "## 6.1.2. 限制多层感知机\n",
    "\n",
    "首先，假设以二维图像 $\\mathbf{X}$ 作为输入，那么我们多层感知机的隐藏表示 $\\mathbf{H}$ 在数学上是一个矩阵，在代码中表示为二维张量。\n",
    "其中 $\\mathbf{X}$ 和 $\\mathbf{H}$ 具有相同的形状。\n",
    "我们可以认为，不仅输入有空间结构，隐藏表示也应该有空间结构。\n",
    "\n",
    "我们用  $[\\mathbf{X}]_{i, j}$ 和 $[\\mathbf{H}]_{i, j}$ 分别表示输入图像和隐藏表示中位置($i$, $j$)处的像素。\n",
    "为了使每个隐藏神经元都接收到每个输入像素的信息，我们将参数从权重矩阵（如同我们先前在多层感知机中所做的那样）替换为四阶权重张量 $\\mathsf{W}$。假设 $\\mathbf{U}$ 包含偏置参数，我们可以将全连接层表示为\n",
    "\n",
    "$$\\begin{aligned} \\left[\\mathbf{H}\\right]_{i, j} &= [\\mathbf{U}]_{i, j} + \\sum_k \\sum_l[\\mathsf{W}]_{i, j, k, l}  [\\mathbf{X}]_{k, l}\\\\ &=  [\\mathbf{U}]_{i, j} +\n",
    "\\sum_a \\sum_b [\\mathsf{V}]_{i, j, a, b}  [\\mathbf{X}]_{i+a, j+b}.\\end{aligned}$$\n",
    "\n",
    "其中，从 $\\mathsf{W}$ 到 $\\mathsf{V}$ 的转换只是形式的转换，因为在两个四阶张量中，系数之间存在一一对应的关系。\n",
    "我们只需重新索引下标 $(k, l)$，使 $k = i+a$、$l = j+b$， 由此 $[\\mathsf{V}]_{i, j, a, b} = [\\mathsf{W}]_{i, j, i+a, j+b}$。\n",
    "这里的索引 $a$ 和 $b$ 覆盖了正偏移和负偏移。\n",
    "对于隐藏表示中任意给定位置（$i$, $j$）处的像素值$[\\mathbf{H}]_{i, j}$，我们可以通过对 $x$ 中以 $(i, j)$ 为中心的像素进行加权求和得到，权重为 $[\\mathsf{V}]_{i, j, a, b}$ 。\n",
    "\n",
    "\n",
    "### 6.1.2.1. 平移不变性\n",
    "\n",
    "现在让我们用上面的第一个原则：平移不变性。\n",
    "这意味着输入 $\\mathbf{X}$ 中的移位，应该仅与隐藏表示 $\\mathbf{H}$ 中的移位相关。也就是说， $\\mathsf{V}$ 和 $\\mathbf{U}$ 实际上不依赖于 $(i, j)$ 的值，即 $[\\mathsf{V}]_{i, j, a, b} = [\\mathbf{V}]_{a, b}$。并且  $\\mathbf{U}$ 是一个常数，比如 $u$。因此，我们可以简化 $\\mathbf{H}$ 定义为：\n",
    "\n",
    "\n",
    "$$[\\mathbf{H}]_{i, j} = u + \\sum_a\\sum_b [\\mathbf{V}]_{a, b} [\\mathbf{X}]_{i+a, j+b}.$$\n",
    "\n",
    "\n",
    "这就是 *卷积* （convolution）。实际上，我们是在使用系数 $[\\mathbf{V}]_{a, b}$ 对位置 $(i, j)$ 附近的像素 $(i+a, j+b)$ 进行加权求和（并加一偏置$u$）来得到$[\\mathbf{H}]_{i, j}$。\n",
    "注意，$[\\mathbf{V}]_{a, b}$ 的参数比 $[\\mathsf{V}]_{i, j, a, b}$ 少很多，因为前者不再依赖于图像中的位置。\n",
    "\n",
    "\n",
    "### 6.1.2.2. 局部性\n",
    "\n",
    "现在引用上述的第二个原则：局部性。如上所述，为了收集用来训练参数 $[\\mathbf{H}]_{i, j}$ 的相关信息，我们不应偏离到距 $(i, j)$ 很远的地方。这意味着在 $|a|> \\Delta$ 或 $|b| > \\Delta$ 的范围之外，我们可以设置 $[\\mathbf{V}]_{a, b} = 0$。由此，我们可以将参数 $[\\mathbf{H}]_{i, j}$ 重写为\n",
    "\n",
    "$$[\\mathbf{H}]_{i, j} = u + \\sum_{a = -\\Delta}^{\\Delta} \\sum_{b = -\\Delta}^{\\Delta} [\\mathbf{V}]_{a, b}  [\\mathbf{X}]_{i+a, j+b}.$$\n",
    ":eqlabel:`eq_conv-layer`\n",
    "\n",
    "简而言之， :eqref:`eq_conv-layer` 是一个 *卷积层* （convolutional layer），而卷积神经网络是包含卷积层的一类特殊的神经网络。\n",
    "在深度学习研究社区中， $\\mathbf{V}$ 被称为 *卷积核* （convolution kernel） 或者  *滤波器* （filter），是可学习的权重。\n",
    "当图像处理的局部区域很小时，卷积神经网络与多层感知机的训练差异可能是巨大的：以前，多层感知机可能需要数十亿个参数来表示，而现在卷积神经网络通常只需要几百个参数，而且不需要改变输入或隐藏表示的维数。\n",
    "参数量的这一减少所付出的代价就是，我们的特征现在必须是平移不变的，且每一层只能包含局部的信息。\n",
    "以上所有的权重学习都依赖于归纳偏置，当这种偏置与实际情况相符时，我们就可以得到有效的模型，这些模型能很好地推广到不可见的数据中。\n",
    "但如果这些假设与实际情况不符，比如当图像不满足平移不变时，我们的模型可能难以拟合。\n",
    "\n",
    "\n",
    "\n",
    "## 6.1.3. 卷积\n",
    "\n",
    "在进一步讨论之前，我们先简要回顾一下为什么上面的操作被称为卷积。在数学中，两个函数（比如 $f, g: \\mathbb{R}^d \\to \\mathbb{R}$）之间的*卷积*被定义为\n",
    "\n",
    "$$(f * g)(\\mathbf{x}) = \\int f(\\mathbf{z}) g(\\mathbf{x}-\\mathbf{z}) d\\mathbf{z}.$$\n",
    "\n",
    "也就是说，卷积是测量 $f$ 和 $g$ 之间（把其中一个函数“翻转”并移位 $\\mathbf{x}$ 时）的重叠。\n",
    "当我们有离散对象时（即定义域为 $\\mathbb{Z}$ ），积分就变成求和，我们得到以下定义：\n",
    "\n",
    "$$(f * g)(i) = \\sum_a f(a) g(i-a).$$\n",
    "\n",
    "对于二维张量，则为 $f$ 在 $(a, b)$ 和 $g$ 在 $(i-a, j-b)$ 上的对应和：\n",
    "\n",
    "$$(f * g)(i, j) = \\sum_a\\sum_b f(a, b) g(i-a, j-b).$$\n",
    ":eqlabel:`eq_2d-conv-discrete`\n",
    "\n",
    "这看起来类似于 :eqref:`eq_conv-layer`，但有一个主要区别：这里不是使用 $(i+a, j+b)$ ，而是使用差值。然而，这种区别是可以化简的，因为我们总是可以匹配  :eqref:`eq_conv-layer` 和 :eqref:`eq_2d-conv-discrete` 之间的符号。我们在 :eqref:`eq_conv-layer` 中的原始定义更正确地描述了*互相关*。我们将在下一节中讨论这一问题。\n",
    "\n",
    "\n",
    "## 6.1.4. “沃尔多在哪里” 回顾\n",
    "\n",
    "回到上面的“沃尔多在哪里”游戏，让我们看看它到底是什么样子。卷积层根据滤波器 $\\mathbf{V}$ 选取给定大小的窗口，并加权处理图片，如 :numref:`fig_waldo_mask` 中所示。我们的目标是学习一个模型，以便探测出在“沃尔多”最可能出现的地方。\n",
    "\n",
    "![发现沃尔多。](../img/waldo-mask.jpg)\n",
    ":width:`400px`\n",
    ":label:`fig_waldo_mask`\n",
    "\n",
    "\n",
    "### 6.1.4.1. 通道\n",
    ":label:`subsec_why-conv-channels`\n",
    "\n",
    "然而这种方法有一个问题：我们忽略了图像一般包含三个通道/三种原色（红色、绿色和蓝色）。\n",
    "实际上，图像不是二维张量，而是一个由高度、宽度和颜色组成的三维张量，比如包含 $1024 \\times 1024 \\times 3$ 个像素。\n",
    "前两个轴与像素的空间位置有关，而第三个轴可以看作是每个像素的多维表示。\n",
    "因此，我们将 $\\mathsf{X}$ 索引为 $[\\mathsf{X}]_{i, j, k}$ 。由此卷积相应地调整为 $[\\mathsf{V}]_{a,b,c}$ ，而不是 $[\\mathbf{V}]_{a,b}$ 。\n",
    "\n",
    "此外，由于输入图像是三维的，我们的隐藏表示 $\\mathsf{H}$ 也最好采用三维张量。\n",
    "换句话说，对于每一个空间位置，我们想要采用一组而不是一个隐藏表示。这样一组隐藏表示可以想象成一些互相堆叠的二维网格。\n",
    "因此，我们可以把隐藏表示想象为一系列具有二维张量的 *通道* （channel）。\n",
    "这些通道有时也被称为 *特征映射* （feature maps），因为每个通道都向后续层提供一组空间化的学习特征。\n",
    "直观上你可以想象在靠近输入的底层，一些通道专门识别边，而其他通道专门识别纹理。\n",
    "\n",
    "为了支持输入 $\\mathsf{X}$ 和隐藏表示 $\\mathsf{H}$ 中的多个通道，我们可以在 $\\mathsf{V}$ 中添加第四个坐标，即 $[\\mathsf{V}]_{a, b, c, d}$ 。综上所述，\n",
    "\n",
    "$$[\\mathsf{H}]_{i,j,d} = \\sum_{a = -\\Delta}^{\\Delta} \\sum_{b = -\\Delta}^{\\Delta} \\sum_c [\\mathsf{V}]_{a, b, c, d} [\\mathsf{X}]_{i+a, j+b, c},$$\n",
    ":eqlabel:`eq_conv-layer-channels`\n",
    "\n",
    "其中隐藏表示 $\\mathsf{H}$ 中的 $d$ 索引表示输出通道，而随后的输出将继续以三维张量 $\\mathsf{H}$ 作为输入进入下一个卷积层。\n",
    "所以， :eqref:`eq_conv-layer-channels` 可以定义具有多个通道的卷积层，而其中 $\\mathsf{V}$ 是该卷积层的权重。\n",
    "\n",
    "然而，仍有许多问题亟待解决。\n",
    "例如，图像中是否到处都有存在沃尔多的可能？如何有效地计算输出层？如何选择适当的激活函数？为了训练有效的网络，如何做出合理的网络设计选择？我们将在本章的其它部分讨论这些问题。\n",
    "\n",
    "\n",
    "\n",
    "## 6.1.5. 小结\n",
    "\n",
    "- 图像的平移不变性使我们可以以相同的方式处理局部图像。\n",
    "- 局部性意味着计算相应的隐藏表示只需一小部分局部图像像素。\n",
    "- 在图像处理中，卷积层通常比全连接层需要更少的参数。\n",
    "- 卷积神经网络（CNN）是一类特殊的神经网络，它可以包含多个卷积层。\n",
    "- 多个输入和输出通道使模型在每个空间位置可以获取图像的多方面特征。\n",
    "\n",
    "\n",
    "\n",
    "## 6.1.6. 练习\n",
    "\n",
    "1. 假设卷积层 :eqref:`eq_conv-layer` 覆盖的局部区域  $\\Delta = 0$  。在这种情况下，证明卷积内核为每组通道独立地实现一个全连接层。\n",
    "1. 为什么平移不变性可能也不是好主意呢？\n",
    "1. 当从图像边界像素获取隐藏表示时，我们需要思考哪些问题？\n",
    "1. 描述一个类似的音频卷积层的架构。\n",
    "1. 卷积层也适合于文本数据吗？为什么？\n",
    "1. 证明在 :eqref:`eq_2d-conv-discrete` 中， $f * g = g * f$  。\n",
    "\n",
    "[Discussions](https://discuss.d2l.ai/t/1846)\n"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "PaddlePaddle 2.1.0 (Python 3.5)",
   "language": "python",
   "name": "py35-paddle1.2.0"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.4"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 1
}
